/**
 * WebHttpRequest.js
 * @author Ulyan Sobin
 * @description WebHttpRequest class
 */

"use strict";

const http = require("http");
const qs = require("querystring");
const cookie = require("../../cookie");
const { WebRequest } = require("../WebRequest");

class WebHttpRequest extends WebRequest {

	/** @type {http.IncomingMessage} */
	origin;

	/** @type {string} */
	method;

	/** @type {object} */
	query;

	/** @type {object} */
	cookies;

	/**
	 * Constructor of `WebHttpRequest`
	 * @param {http.IncomingMessage} request 
	 */
	constructor(request) {
		if (!(request instanceof http.IncomingMessage))
			throw new Error("Type error: \'request\'");
		let divide = request.url.split('?');
		super(divide[0]);
		this.origin = request;
		this.method = request.method;
		if (divide.length > 1)
			this.query = qs.parse(divide[1]);
		else
			this.query = {};
		if (request.headers.cookie)
			this.cookies = cookie.CookieSet.parse(request.headers.cookie);
		else
			this.cookies = {};
	}

	/**
	 * 
	 * @private
	 * @type {boolean}
	 */
	#acceptedPostData = false;

	/**
	 * 
	 * @param { number } limit
	 * @returns {Promise<Uint8Array>}
	 */
	acceptPostData(limit) {
		if (this.#acceptedPostData) throw new Error("Data accepted");
		this.#acceptedPostData = true;
		return new Promise((resolve, reject) => {
			if (this.method != "POST")
				reject(new Error("Request method is not \'POST\'"));
			else {
				let coll = [];
				let totalLength = 0;
				this.origin.on("data", (chunk) => {
					if (typeof(limit) == "number" && totalLength + chunk.length > limit) {
						this.request.pause();
						throw new Error("Post data out of size");
					}
					else {
						coll.push(chunk);
						totalLength += chunk.length;
					}
				});
				this.origin.on("end", () => {
					let data = new Uint8Array(totalLength);
					let i = 0;
					for (let chunk of coll) {
						for (let byte of chunk) data[i++] = byte;
					}
					resolve(data);
				});
			}
		});
	}

	/**
	 * 
	 * @param {function(Uint8Array)} callback
	 */
	whenOnData(callback) {
		if (this.#acceptedPostData) throw new Error("Data accepted");
		if (this.method != "POST")
			throw new Error("Request method is not POST");
		this.request.on("data", (data) => {
			callback(data);
		});
	}

	/**
	 * 
	 * @param {function()} callback
	 */
	whenOnDataEnd(callback) {
		if (this.#acceptedPostData) throw new Error("Data accepted");
		if (this.method != "POST")
			throw new Error("Request method is not POST");
		this.request.on("end", () => {
			callback();
		});
	}

	/**
	 * Accept POST data as JSON
	 * @param { number } limit
	 * @returns { Promise<any> }
	 */
	acceptJSON(limit) {
		return this.acceptPostData(limit)
		.then((data) => {
			return new Promise((resolve, reject) => {
				try {
					let json = JSON.parse(new TextDecoder().decode(data));
					resolve(json);
				} catch (err) {
					reject(err);
				}
			});
		});
	}

	/**
	 * 
	 * @param { number } limit
	 * @returns { Object.<string, string> }
	 */
	acceptFormData(limit) {
		return this.acceptPostData(limit)
		.then((data) => {
			return new Promise((resolve, reject) => {
				try {
					let result = {};
					const divider = /\-+[A-Za-z0-9]+[\-|\s]+/;
					const content = new TextDecoder().decode(data);
					const divs = content.split(divider);
					const ln = divs.length - 1;
					for (let i = 1; i < ln; i++) {
						let item = divs[i].trim();
						let r1 = item.replace(/Content-Disposition: *form-data; *name=\"/, "")
						let pos = r1.match(/[\"]{1}[\n|\r|\r\n]{2}/).index;
						let name = r1.substring(0, pos).trim();
						let body = r1.substring(pos + 3).trim();
						result[name] = body;
					}
					resolve(result);
				} catch (err) {
					reject(err);
				}
			});
		});
	}
}

module.exports.WebHttpRequest = WebHttpRequest;